<?php
/*
 |------------------------------------------------------------------------------
 | vendor Connection
 |------------------------------------------------------------------------------
 |
 | Time: 2017-10-11  15:20
 |
 | Describe: models 的抽象类 （pdo 操作方法）
 |
 */

namespace Jason\Db\Db;
use PDO;
use Jason\Db\Db;

abstract class Connection
{

    /** @var PDOStatement PDO操作实例 */
    protected $PDOStatement;

    /** @var string 当前SQL指令 */
    protected $queryStr = '';
    // 返回或者影响记录数
    protected $numRows = 0;
    // 事务指令数
    protected $transTimes = 0;
    // 错误信息
    protected $error = '';

    /** @var PDO[] 数据库连接ID 支持多个连接 */
    protected $links = [];

    /** @var PDO 当前连接ID */
    protected $linkID;
    protected $linkRead;
    protected $linkWrite;

    // 查询结果类型
    protected $resultSetType = 'array';
    // 查询结果类型
    protected $fetchType = PDO::FETCH_ASSOC;
    // 字段属性大小写
    protected $attrCase = PDO::CASE_LOWER;
    // 数据库连接参数配置
    protected $config = [
        // 数据库类型
        'type'           => '',
        // 服务器地址
        'hostname'       => '',
        // 数据库名
        'database'       => '',
        // 用户名
        'username'       => '',
        // 密码
        'password'       => '',
        // 端口
        'hostport'       => '',
        // 连接dsn
        'dsn'            => '',
        // 数据库连接参数
        'params'         => [],
        // 数据库编码默认采用utf8
        'charset'        => 'utf8',
        // 数据库表前缀
        'prefix'         => '',
        // 数据库调试模式
        'debug'          => false,
        // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
        'deploy'         => 0,
        // 数据库读写是否分离 主从式有效
        'rw_separate'    => false,
        // 读写分离后 主服务器数量
        'master_num'     => 1,
        // 指定从服务器序号
        'slave_no'       => '',
        // 是否严格检查字段是否存在
        'fields_strict'  => true,
        // 数据集返回类型
        'resultset_type' => 'array',
        // 自动写入时间戳字段
        'auto_timestamp' => false,
        // 是否需要进行SQL性能分析
        'sql_explain'    => false,
        //是否写日志
        'is_log'    => false,
    ];

    // PDO连接参数
    protected $params = [
        PDO::ATTR_CASE              => PDO::CASE_NATURAL,
        PDO::ATTR_ERRMODE           => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_ORACLE_NULLS      => PDO::NULL_NATURAL,
        PDO::ATTR_STRINGIFY_FETCHES => false,
        PDO::ATTR_EMULATE_PREPARES  => false,
    ];

    /**
     * 架构函数 读取数据库配置信息
     * @access public
     * @param array $config 数据库配置数组
     */
    public function __construct(array $config = [])
    {
        if (!empty($config)) {
            $this->config = array_merge($this->config, $config);
        }
    }
    /**
     * 解析pdo连接的dsn信息
     * @access protected
     * @param array $config 连接信息
     * @return string
     */
    abstract protected function parseDsn($config);

    /**
     * 取得数据表的字段信息
     * @access public
     * @param string $tableName
     * @return array
     */
    abstract public function getFields($tableName);

    /**
     * 取得数据库的表信息
     * @access public
     * @param string $dbName
     * @return array
     */
    abstract public function getTables($dbName);

    /**
     * SQL性能分析
     * @access protected
     * @param string $sql
     * @return array
     */
    abstract protected function getExplain($sql);

    /**
     * 对返数据表字段信息进行大小写转换出来
     * @access public
     * @param array $info 字段信息
     * @return array
     */
    public function fieldCase($info)
    {
        // 字段大小写转换
        switch ($this->attrCase) {
            case PDO::CASE_LOWER:
                $info = array_change_key_case($info);
                break;
            case PDO::CASE_UPPER:
                $info = array_change_key_case($info, CASE_UPPER);
                break;
            case PDO::CASE_NATURAL:
            default:
                // 不做转换
        }
        return $info;
    }

    /**
     * 获取数据库的配置参数
     * @access public
     * @param string $config 配置名称
     * @return mixed
     */
    public function getConfig($config = '')
    {
        return $config ? $this->config[$config] : $this->config;
    }

    /**
     * 设置数据库的配置参数
     * @access public
     * @param string|array      $config 配置名称
     * @param mixed             $value 配置值
     * @return void
     */
    public function setConfig($config, $value = '')
    {
        if (is_array($config)) {
            $this->config = array_merge($this->config, $config);
        } else {
            $this->config[$config] = $value;
        }
    }

    /**
     * 连接数据库方法
     * @access public
     * @param array         $config 连接参数
     * @param integer       $linkNum 连接序号
     * @param array|bool    $autoConnection 是否自动连接主数据库（用于分布式）
     * @return PDO
     * @throws Exception
     */
    public function connect(array $config = [], $linkNum = 0, $autoConnection = false)
    {
        if (!isset($this->links[$linkNum])) {
            if (!$config) {
                $config = $this->config;
            } else {
                $config = array_merge($this->config, $config);
            }
            // 连接参数
            if (isset($config['params']) && is_array($config['params'])) {
                $params = $config['params'] + $this->params;
            } else {
                $params = $this->params;
            }
            // 记录当前字段属性大小写设置
            $this->attrCase = $params[PDO::ATTR_CASE];
            // 记录数据集返回类型
            if (isset($config['resultset_type'])) {
                $this->resultSetType = $config['resultset_type'];
            }
            try {
                if (empty($config['dsn'])) {
                    $config['dsn'] = $this->parseDsn($config);
                }
                if ($config['debug']) {
                    $startTime = microtime(true);
                }
                if ($config['type'] == 'sqlsrv') {
                    $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password']);

                } else {
                    $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params);

                }
                if ($config['debug']) {
                    // 记录数据库连接信息
                    $message = '[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']. 'sql';
                    $this->write($message);
                }
            } catch (\PDOException $e) {
                if ($autoConnection) {
                    $this->write($e->getMessage());
                    return $this->connect($autoConnection, $linkNum);
                } else {
                    $this->write($e->getMessage());
                }
            }
        }
        return $this->links[$linkNum];
    }

    /**
     * 释放查询结果
     * @access public
     */
    public function free()
    {
        $this->PDOStatement = null;
    }

    /**
     * 获取PDO对象
     * @access public
     * @return \PDO|false
     */
    public function getPdo()
    {
        if (!$this->linkID) {
            return false;
        } else {
            return $this->linkID;
        }
    }

    /**
     * 执行查询 返回数据集
     * @access public
     * @param string        $sql sql指令
     * @param array         $bind 参数绑定
     * @param boolean       $master 是否在主服务器读操作
     * @param bool|string   $class 指定返回的数据集对象
     * @return mixed
     * @throws BindParamException
     * @throws PDOException
     */
    public function query($sql, $bind = [],$all = false, $master = false)
    {
        $this->initConnect($master);
        if (!$this->linkID) {
            return false;
        }
        // 根据参数绑定组装最终的SQL语句
        $this->queryStr = $this->getRealSql($sql, $bind, $master);
        //释放前次的查询结果
        if (!empty($this->PDOStatement)) {
            $this->free();
        }
        Db::$queryTimes++;
        try {

            $this->PDOStatement = $this->linkID->prepare($sql);
            if (false === $this->PDOStatement) {
                $error = '[ PDO ] prepare is false [ SQL ] '.$this->queryStr;
                $this->write($error);
            }

            // 参数绑定
            $this->bindValue($bind);
            // 执行查询
            $result = $this->PDOStatement->execute();
            if (false === $result) {
                $error = '[ PDO ] execute is false [ SQL ] '.$this->queryStr;
                $this->write($error);
            }
            if ($all === true) {
                $data = $this->PDOStatement->fetch($this->fetchType);
            } else {
                $data = $this->PDOStatement->fetchAll($this->fetchType);
            }
           return $data;
        } catch (\Exception $e) {
            $error = $e->getMessage();
            $this->write($error);
            return false;
        }
    }

    /**
     * 执行语句
     * @access public
     * @param string        $sql sql指令
     * @param array         $bind 参数绑定
     * @return int
     * @throws BindParamException
     * @throws PDOException
     */
    public function execute($sql, $bind = [])
    {
        $this->initConnect(true);
        if (!$this->linkID) {
            return false;
        }
        // 根据参数绑定组装最终的SQL语句
        $this->queryStr = $this->getRealSql($sql, $bind, true);

        //释放前次的查询结果
        if (!empty($this->PDOStatement)) {
            $this->free();
        }
        Db::$executeTimes++;
        try {
            // 预处理
            $this->PDOStatement = $this->linkID->prepare($sql);
            if (false === $this->PDOStatement) {
                $error = '[ PDO ] prepare is false [ SQL ] '.$this->queryStr;
                $this->write($error);
            }
            // 参数绑定操作
            $this->bindValue($bind);
            // 执行语句
            $result = $this->PDOStatement->execute();
            if (false === $result) {
                $error = '[ PDO ] execute is false [ SQL ] '.$this->queryStr;
                $this->write($error);
            }
            // 调试结束


            $this->numRows = $this->PDOStatement->rowCount();
            return $this->numRows;
        } catch (\PDOException $e) {
            $error = $e->getMessage();
            $this->write($error);
        }
    }

    /**
     * 根据参数绑定组装最终的SQL语句 便于调试
     * @access public
     * @param string    $sql 带参数绑定的sql语句
     * @param array     $bind 参数绑定列表
     * @return string
     */
    public function getRealSql($sql, array $bind = [], $master = false)
    {
        if ($bind) {
            foreach ($bind as $key => $val) {
                $value = is_array($val) ? $val[0] : $val;
                $type  = is_array($val) ? $val[1] : PDO::PARAM_STR;
                if (PDO::PARAM_STR == $type) {
                    $value = $this->quote($value, $master);
                }
                // 判断占位符
                $sql = is_numeric($key) ?
                substr_replace($sql, $value, strpos($sql, '?'), 1) :
                str_replace(
                    [':' . $key . ')', ':' . $key . ',', ':' . $key . ' '],
                    [$value . ')', $value . ',', $value . ' '],
                    $sql . ' ');
            }
        }
        return $sql;
    }

    /**
     * 参数绑定
     * 支持 ['name'=>'value','id'=>123] 对应命名占位符
     * 或者 ['value',123] 对应问号占位符
     * @access public
     * @param array $bind 要绑定的参数列表
     * @return void
     */
    protected function bindValue(array $bind = [])
    {
        if ($bind) {
            foreach ($bind as $key => $val) {
                $param = is_numeric($key) ? $key + 1 : ':' . $key;

                if (is_numeric($val)) {
                    $param_type = PDO::PARAM_INT;
                } else {
                    $param_type = PDO::PARAM_STR;
                }
                $result = $this->PDOStatement->bindValue($param, $val, $param_type);
                if (!$result) {
                    $error = '[ PDO ] bindValue is false [BIND] : ' . print_r($val, true);
                    $this->write($error);
                }
            }
        }
    }





    /**
     * 执行数据库事务
     * @access public
     * @param callable $callback 数据操作方法回调
     * @return mixed
     * @throws PDOException
     * @throws \Exception
     * @throws \Throwable
     */
    public function transaction($callback)
    {
        $this->startTrans();
        try {
            $result = null;
            if (is_callable($callback)) {
                $result = call_user_func_array($callback, [$this]);
            }
            $this->commit();
            return $result;
        } catch (\Exception $e) {
            $this->rollback();
            throw $e;
        } catch (\Throwable $e) {
            $this->rollback();
            throw $e;
        }
    }

    /**
     * 启动事务
     * @access public
     * @return void
     */
    public function startTrans()
    {
        $this->initConnect(true);
        if (!$this->linkID) {
            return false;
        }

        ++$this->transTimes;

        if (1 == $this->transTimes) {
            $this->linkID->beginTransaction();
        } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
            $this->linkID->exec(
                $this->parseSavepoint('trans' . $this->transTimes)
            );
        }
    }

    /**
     * 用于非自动提交状态下面的查询提交
     * @access public
     * @return void
     * @throws PDOException
     */
    public function commit()
    {
        $this->initConnect(true);

        if (1 == $this->transTimes) {
            $this->linkID->commit();
        }

        --$this->transTimes;
    }

    /**
     * 事务回滚
     * @access public
     * @return void
     * @throws PDOException
     */
    public function rollback()
    {
        $this->initConnect(true);

        if (1 == $this->transTimes) {
            $this->linkID->rollBack();
        } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
            $this->linkID->exec(
                $this->parseSavepointRollBack('trans' . $this->transTimes)
            );
        }

        $this->transTimes = max(0, $this->transTimes - 1);
    }

    /**
     * 是否支持事务嵌套
     * @return bool
     */
    protected function supportSavepoint()
    {
        return false;
    }

    /**
     * 生成定义保存点的SQL
     * @param $name
     * @return string
     */
    protected function parseSavepoint($name)
    {
        return 'SAVEPOINT ' . $name;
    }

    /**
     * 生成回滚到保存点的SQL
     * @param $name
     * @return string
     */
    protected function parseSavepointRollBack($name)
    {
        return 'ROLLBACK TO SAVEPOINT ' . $name;
    }

    /**
     * 批处理执行SQL语句
     * 批处理的指令都认为是execute操作
     * @access public
     * @param array $sqlArray SQL批处理指令
     * @return boolean
     */
    public function batchQuery($sqlArray = [])
    {
        if (!is_array($sqlArray)) {
            return false;
        }
        // 自动启动事务支持
        $this->startTrans();
        try {
            foreach ($sqlArray as $sql) {
                $this->execute($sql);
            }
            // 提交事务
            $this->commit();
        } catch (\Exception $e) {
            $this->rollback();
            throw $e;
        }
        return true;
    }

    /**
     * 获得查询次数
     * @access public
     * @param boolean $execute 是否包含所有查询
     * @return integer
     */
    public function getQueryTimes($execute = false)
    {
        return $execute ? Db::$queryTimes + Db::$executeTimes : Db::$queryTimes;
    }

    /**
     * 获得执行次数
     * @access public
     * @return integer
     */
    public function getExecuteTimes()
    {
        return Db::$executeTimes;
    }

    /**
     * 关闭数据库
     * @access public
     */
    public function close()
    {
        $this->linkID = null;
    }

    /**
     * 获取最近一次查询的sql语句
     * @access public
     * @return string
     */
    public function getLastSql()
    {
        return $this->queryStr;
    }

    /**
     * 获取最近插入的ID
     * @access public
     * @param string  $sequence     自增序列名
     * @return string
     */
    public function getLastInsID($sequence = null)
    {
        return $this->linkID->lastInsertId($sequence);
    }

    /**
     * 获取返回或者影响的记录数
     * @access public
     * @return integer
     */
    public function getNumRows()
    {
        return $this->numRows;
    }
    public function write($str)
    {
        if ($this->config['is_log']) {
            //调用 log类写入日志
            echo $str;
        }
        return true;
    }
    /**
     * 获取最近的错误信息
     * @access public
     * @return string
     */
    public function getError()
    {
        if ($this->PDOStatement) {
            $error = $this->PDOStatement->errorInfo();
            $error = $error[1] . ':' . $error[2];
        } else {
            $error = '';
        }
        if ('' != $this->queryStr) {
            $error .= "\n [ SQL语句 ] : " . $this->queryStr;
        }
        return $error;
    }

    /**
     * SQL指令安全过滤
     * @access public
     * @param string $str SQL字符串
     * @param bool   $master 是否主库查询
     * @return string
     */
    public function quote($str, $master = true)
    {
        $this->initConnect($master);
        return $this->linkID ? $this->linkID->quote($str) : $str;
    }

    /**
     * 数据库调试 记录当前SQL及分析性能
     * @access protected
     * @param boolean $start 调试开始标记 true 开始 false 结束
     * @param string  $sql 执行的SQL语句 留空自动获取
     * @return void
     */
    protected function debug($start, $sql = '')
    {
        if (!empty($this->config['debug'])) {
            // 开启数据库调试模式
            if ($start) {
                $sql     = $sql ?: $this->queryStr;
                if ($this->config['sql_explain'] && 0 === stripos(trim($sql), 'select')) {
                    $result = $this->getExplain($sql);
                }

            }
        }
    }


    /**
     * 初始化数据库连接
     * @access protected
     * @param boolean $master 是否主服务器
     * @return void
     */
    protected function initConnect($master = true)
    {
        if (!empty($this->config['deploy'])) {
            // 采用分布式数据库
            if ($master) {
                if (!$this->linkWrite) {
                    $this->linkWrite = $this->multiConnect(true);
                }
                $this->linkID = $this->linkWrite;
            } else {
                if (!$this->linkRead) {
                    $this->linkRead = $this->multiConnect(false);
                }
                $this->linkID = $this->linkRead;
            }
        } elseif (!$this->linkID) {
            // 默认单数据库
            $this->linkID = $this->connect();
        }
    }

    /**
     * 连接分布式服务器
     * @access protected
     * @param boolean $master 主服务器
     * @return PDO
     */
    protected function multiConnect($master = false)
    {
        $_config = [];
        // 分布式数据库配置解析
        foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
            $_config[$name] = explode(',', $this->config[$name]);
        }

        // 主服务器序号
        $m = floor(mt_rand(0, $this->config['master_num'] - 1));

        if ($this->config['rw_separate']) {
            // 主从式采用读写分离
            if ($master) // 主服务器写入
            {
                $r = $m;
            } elseif (is_numeric($this->config['slave_no'])) {
                // 指定服务器读
                $r = $this->config['slave_no'];
            } else {
                // 读操作连接从服务器 每次随机连接的数据库
                $r = floor(mt_rand($this->config['master_num'], count($_config['hostname']) - 1));
            }
        } else {
            // 读写操作不区分服务器 每次随机连接的数据库
            $r = floor(mt_rand(0, count($_config['hostname']) - 1));
        }
        $dbMaster = false;
        if ($m != $r) {
            $dbMaster = [];
            foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
                $dbMaster[$name] = isset($_config[$name][$m]) ? $_config[$name][$m] : $_config[$name][0];
            }
        }
        $dbConfig = [];
        foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
            $dbConfig[$name] = isset($_config[$name][$r]) ? $_config[$name][$r] : $_config[$name][0];
        }
        return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster);
    }
    /**
     * 添加数据
     * 默认预处理
     * @param string $tableName  数据表名
     * @param array $data  要插入的数据： 数组形式
     * @param boolean $replace 是否采用replace模式插入，默认为false
     * @return number|boolean 返回新增id
     */
    public function insert( $tableName , $data, $replace = false) {
        $fieldAry = array_keys($data);
        $placeAry = array_fill(0, count($data), '?');
        $bindAry  = array_values($data);
        $sql      = ($replace ? 'REPLACE' : 'INSERT') . ' INTO ' . $tableName . ' ('.implode(',', $fieldAry).') VALUES ('. implode(',', $placeAry) .')';
        $rows = $this->execute($sql,$bindAry);
        if ($rows) {
            return $this->getLastInsID();
        } else {
            $error = '[ INSERT ] sql: '.$this->queryStr;
            $this->write($error);
            return false;
        }
    }
    /**
     * 按照sql语句插入数据
     * @param string $sql
     */
    public function insertBySql( $sql ) {
        $row =  $this->execute($sql, []);
        if (!$row) {
            $error = '[ INSERTBYSQL ] sql: '.$this->queryStr;
            $this->write($error);
        }
        return $row;
    }
    /**
     * 更新操作
     * 当where条件为数组形式时，会走预处理
     * @param unknown $tableName
     * @param array $data
     * @param string|array $where
     */
    public function update( $tableName, $data, $where ) {
        if (! $where) {
            $error = '[ UPDATE ] 更新语句缺少where条件 ';
            $this->write($error);
        }
        $fieldArr = [];
        $bindArr  = [];
        foreach ($data as $key => $val) {
            $key = $this->removeFilterBadChar($key);
            $fieldArr[] = $key.'=?';
            $bindArr[] = $val;
        }
        if (is_array($where)) {
            $whereArr = $where;
            $where    = $this->arrayToWhere($whereArr);
        } else {
            $whereArr = [];
        }
        $sql = "UPDATE `{$tableName}` SET " . join(',', $fieldArr) . ' WHERE ' . $where;

        if (is_array($whereArr) && !empty($whereArr)) {
            foreach ($whereArr as $v) {
                $bindArr[] = $v;
            }
        }

        $row =  $this->execute($sql, $bindArr);
        if ($row === false) {
            $error = '[ UPDATE ] sql: '.$this->queryStr;
            $this->write($error);
        }
        return $row;
    }

    /**
     * 按照sql语句更新
     *
     * @param string $sql     建议传递预处理sql，用问号做占位符
     * @param array  $bindArr 需要预处理绑定的值
     */
    public function updateBySql($sql, $bindArr = [])
    {
        if (false === stripos($sql, 'where')) {
            $error = '[ UPDATE ] 更新语句缺少where条件 ';
            $this->write($error);
        }
        $row =  $this->execute($sql, $bindArr);
        if ($row === false) {
            $error = '[ UPDATEBYSQL ] sql: '.$this->queryStr;
            $this->write($error);
        }
        return $row;
    }
    /**
     * 删除
     * 当where条件为数组形式时，会走预处理
     * @param unknown $table
     * @param unknown $where
     */
    public function delete( $tableName, $where ) {
        if (! $where) {
            $error = '[ DELETE ] 删除语句缺少where条件 ';
            $this->write($error);
        }

        if (is_array($where)) {
            $bindArr = $where;
            $where   = $this->arrayToWhere($bindArr);
        } else {
            $bindArr = [];
        }

        $sql = "DELETE FROM `{$tableName}` WHERE {$where}";

        $row =  $this->execute($sql, $bindArr);
        if ($row === false) {
            $error = '[ UPDATEBYSQL ] sql: '.$this->queryStr;
            $this->write($error);
        }
        return $row;
    }
    /**
     * 获取一条数据
     * 当条件为数组形式时，会走预处理
     * @param string $tableName
     * @param string|array $where 条件
     * @param array $selectFileds 可以指定抓取的字段
     * @param string $order 例如：id DESC
     * @param string $master 是否从主库中读取，默认从从库中获取
     */
    public function selectOne( $tableName, $where, $selectFileds = [], $order = null , $master = false) {
        if (empty($where))
        {
            $error = '[ SELECTALL ] 查询缺少 where 条件 或 where 条件为空';
            $this->write($error);
            return false;
        }
        $fields = '*';
        if (! empty($selectFileds)) {
            $fields = implode(',', $selectFileds);
        }

        if (is_array($where)) {
            $bindArr = $where;
            $where   = $this->arrayToWhere($bindArr);
        } else {
            $bindArr = [];
        }
        $sql = "SELECT {$fields} FROM `{$tableName}` WHERE {$where}";
        if ($order) {
            $sql .= " ORDER BY {$order}";
        }
        return $this->query($sql, $bindArr ,true ,$master);
    }
    /**
     * 组合查询
     * 当条件为数组形式时，会走预处理
     * @param string $tableName
     * @param string|array $where
     * @param array $selectFields
     * @param string $order
     * @param string $limit
     * @param boolean $master 是否从主库中读取，默认从从库中获取
     */
    public function selectAll( $tableName, $where, $selectFields = array(), $order = null, $limit = null , $master = false ) {
        if (empty($where))
        {
            $error = '[ SELECTALL ] 查询缺少 where 条件 或 where 条件为空';
            $this->write($error);
            return false;
        }
        $fields = '*';
        if (! empty($selectFields)) {
            $fields = implode(',', $selectFields);
        }

        if (is_array($where)) {
            $bindArr = $where;
            $where   = $this->arrayToWhere($bindArr);
        } else {
            $bindArr = [];
        }
        $sql = "SELECT {$fields} FROM `{$tableName}` WHERE {$where}";
        if ($order) {
            $sql .= " ORDER BY {$order}";
        }
        if ($limit) {
            $sql .= " LIMIT {$limit}";
        }
        return $this->query($sql, $bindArr ,false ,$master);
    }

    /**
     * 按照sql语句查询
     * @param unknown $sql
     */
    public function setlectBySql( $sql ,  $bindArr=[], $master = false) {
        return $this->query($sql, $bindArr,false, $master);
    }
    /**
     * 将条件数组，拼凑预处理sql
     * @param array $filter
     * @return string
     *支持的格式
     * array('id'=>2)
     * array('add_time >'=>'2016-12-19')
     * array('add_time >='=>'2016-12-19')
     * array('add_time ='=>'2016-12-19')
     * array('add_time !='=>'2016-12-19')
     * array('title LIKE'=>'%微微%')
     * array('id ='=>array(1,2,3,4)) 与 array('id'=>array(1,2,3,4)) 相同
     */
    public function arrayToWhere(&$filter)
    {
        if (!$filter) {
            return '';
        }

        $where = '';
        $sql = '';
        $parm = [];
        foreach ($filter as $k => $v) {
            // 过滤 key
            $k = $this->removeFilterBadChar($k);

            if (is_array($v)) {
                // 例如：array('id ='=>array(1,2,3,4))
                if (!$v) continue;
                $where = $k . ' IN(' . implode(',', array_fill(0, count($v), '?')) . ')';
                foreach ($v as $v1) {
                    $parm[] = $v1;
                }
            } elseif (strpos($k, '>') || strpos($k, '<') || strpos($k, '!')  || strpos($k, '=') || stripos($k, 'LIKE') || stripos($k, '&')|| stripos($k, '(') !== false || stripos($k, ')')) {
                // 例如：array('add_time >'=>'2016-12-19')
                // array('add_time >='=>'2016-12-19')
                // array('add_time ='=>'2016-12-19')
                // array('add_time !='=>'2016-12-19')
                // array('title LIKE'=>'%微微%')
                if (stripos($k, ')')) {
                    $where = trim($k,') ') . ' ? )';
                } else {
                    $where = $k . '?';
                }

                $parm[] = $v;
            } else {
                // 例如：array('id'=>2)
                $where = $k . '=?';
                $parm[] = $v;
            }

            if (!$sql) {
                $sql = " {$where}";
            } else {
                // 例如：array('id'=>2, 'OR id'=>3)
                $sql = $sql . ' ' . ((false !== stripos($k, 'OR ') || false !== stripos($k, 'AND ')) ? '': 'AND ') . $where;
            }
        }
        $filter = $parm;

        return $sql;
    }
    /**
     * 过滤 Filter 中的非法字符
     */
    public function removeFilterBadChar($array)
    {
        if (is_numeric($array)) {
            return $array;
        } elseif (!is_array($array)) {
            return str_replace(array('"', "'", ',', ';', '*', '#', '/', '\\', '%'), '', $array);
        }

        foreach ($array as $k => $v) {
            $array[$k] = $this->removeFilterBadChar($v);
        }
        return $array;
    }
    /**
     * 析构方法
     * @access public
     */
    public function __destruct()
    {
        // 释放查询
        if ($this->PDOStatement) {
            $this->free();
        }
        // 关闭连接
        $this->close();
    }
}
